package cz.cvut.fel.taskpro.data.manager;

import cz.cvut.fel.taskpro.business.support.Maintance;
import cz.cvut.fel.taskpro.data.entity.Assignment;
import cz.cvut.fel.taskpro.data.entity.AssignmentSubmit;
import cz.cvut.fel.taskpro.data.entity.AssignmentSubmit.State;
import cz.cvut.fel.taskpro.data.entity.GroupTaskPro;
import cz.cvut.fel.taskpro.data.entity.Kantor;
import cz.cvut.fel.taskpro.data.entity.Student;
import cz.cvut.fel.taskpro.data.entity.Subject;
import cz.cvut.fel.taskpro.data.entity.Team;
import cz.cvut.fel.taskpro.data.entity.Template;
import cz.cvut.fel.taskpro.data.entity.TemplateFile;
import cz.cvut.fel.taskpro.data.entity.Version;
import cz.cvut.fel.taskpro.data.imanager.IAssignmentData;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
/**
 * This class implement #{@link IAssignmentData} interface
 * @author Tom
 * @version 1.0. beta
 */

@Stateless
public class AssignmentData extends ObjectManager implements IAssignmentData {

    public AssignmentData() {
    }

    @Override
    public void removeTemplateFile(TemplateFile templateFileToRemove) {
        super.remove(templateFileToRemove);
    }

    @Override
    public void updateTemplate(Template template) {
        super.update(template);
    }

    @Override
    public void addTemplate(Template newTemplate) {
        super.add(newTemplate);
    }

    @Override
    public List<Template> getTemplatesByKantor(Kantor kantor) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Template> templateQuery = cb.createQuery(Template.class);
        Root<Template> rootTemplateQuery = templateQuery.from(Template.class);
        Predicate kantorPredicate = cb.equal(rootTemplateQuery.get("kantor"), kantor);
        templateQuery.distinct(true).where(kantorPredicate).orderBy(cb.desc(rootTemplateQuery.get("dateOfCreation")));
        TypedQuery typedQuery = em.createQuery(templateQuery);
        List<Template> resultList = typedQuery.getResultList();
        return resultList;
    }

    @Override
    public void assignTemplate(List<Assignment> assignments) {
        Iterator assignIt = assignments.iterator();
        while (assignIt.hasNext()) {
            Assignment currAssignment = (Assignment) assignIt.next();
            if (currAssignment.getmStudents() != null) {
                for (Student student : currAssignment.getmStudents()) {
                    student.addAssingment(currAssignment);
                    super.update(student);
                }
            } else if (currAssignment.getmGroups() != null) {
                for (GroupTaskPro group : currAssignment.getmGroups()) {
                    group.addAssignment(currAssignment);
                    super.update(group);
                }
            } else if (currAssignment.getmTeams() != null) {
                for (Team team : currAssignment.getmTeams()) {
                    team.addAssignment(currAssignment);
                    super.update(team);
                }
            }  
             super.add(currAssignment);
        }
    }

    @Override
    public void updateAssignmentSubmit(AssignmentSubmit assignmentSubmit) {
        super.update(assignmentSubmit);
    }

    @Override
    public List<AssignmentSubmit> getAssignmentSubmitByContext(Assignment assignment, Student student, List<AssignmentSubmit.State> states) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<AssignmentSubmit> query = cb.createQuery(AssignmentSubmit.class);
        Root<AssignmentSubmit> assignments = query.from(AssignmentSubmit.class);
        Predicate studentPred = cb.equal(assignments.get("student"), student);
        Predicate assginmetPred = cb.equal(assignments.get("assignment"), assignment);
        if ((states == null) || (states.isEmpty())) {
            query.where(
                    cb.and(studentPred, assginmetPred));
        } else if (states.size() == 1) {
            Predicate statePred = cb.equal(assignments.get("stateOfTask"), states.get(0));
            query.where(
                    cb.and(statePred, studentPred, assginmetPred));
        } else {
            List<Predicate> predicates = new ArrayList<Predicate>();
            Iterator stIterator = states.iterator();
            while (stIterator.hasNext()) {
                predicates.add(cb.equal(assignments.get("stateOfTask"), stIterator.next()));
            }
            Predicate statePred = cb.or(predicates.toArray(new Predicate[0]));
            query.where(
                    cb.and(statePred, studentPred, assginmetPred));
        }
        List<AssignmentSubmit> result = em.createQuery(query).getResultList();
        return result;
    }

    @Override
    public boolean isAssignmentSubmitExist(Assignment assignment, Student student, AssignmentSubmit.State state, Team teamOnAssignment) {
        List<AssignmentSubmit.State> states = new ArrayList<AssignmentSubmit.State>();
        states.add(state);
        List<AssignmentSubmit> result;
        if (assignment.getType().equals(Assignment.Type.TEAM)) {
            result = getTeamAssignmentSubmitByContext(assignment, teamOnAssignment, states);
        } else {
            result = getAssignmentSubmitByContext(assignment, student, states);
        }
        if (result.isEmpty()) {
            return false;
        } else {
            return true;
        }
    }

    @Override
    public List<Assignment> assignmentToSubmit(Student student) {
        List<Assignment> resultAssignments = new ArrayList<Assignment>();
        resultAssignments.addAll(getUnSubmitAssingnment(student, true, null));
        resultAssignments.addAll(getUnSubmitAssignment(student.getmGroups(), student, true, null));
        resultAssignments.addAll(getUnSubmitTeamAssignmet(student, true, null));
        List<AssignmentSubmit.State> states = new ArrayList<AssignmentSubmit.State>();
        states.add(AssignmentSubmit.State.REJECTED);
        states.add(AssignmentSubmit.State.SUBMITTED);
        resultAssignments.addAll(getAssignmentSubByTeam(student, states, true));
        resultAssignments.addAll(getAssignmentSubByContext(student, states, true));
        return resultAssignments;
    }

    @Override
    public List<Assignment> getUnSubmitAssingnment(Student student, boolean areBeforeDeadline, Subject subject) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Assignment> query = cb.createQuery(Assignment.class);
        Root<Assignment> assignments = query.from(Assignment.class);
        Subquery notSub = query.subquery(AssignmentSubmit.class);
        Root notSubRoot = notSub.from(AssignmentSubmit.class);
        Predicate subjectPred = null;
        Predicate studentOnNotSubmit = cb.equal(notSubRoot.get("student"), student);
        Predicate assignmentOnNotSubmit = cb.equal(notSubRoot.get("assignment"), assignments);
        Predicate studentOnAssingment = cb.equal(assignments.get("mStudents"), student);
        notSub.where(cb.and(studentOnNotSubmit, assignmentOnNotSubmit));
        notSub.distinct(true);
        Predicate taskDate = null;
        Date currDate = Maintance.getTodayDate();
        if (areBeforeDeadline) {
            Path<Date> date = assignments.get("deadline");
            taskDate = cb.greaterThan(date, currDate);
        }
        Path<Date> dateOfStart = assignments.get("dateOfStart");
        Predicate startTimePredicate = cb.lessThan(dateOfStart, currDate);
        Predicate typAssignment = cb.equal(assignments.get("type"), Assignment.Type.STUDENT);
        if ((subject != null) && (taskDate != null)) {
            subjectPred = cb.equal(assignments.get("subject"), subject);
            query.where(cb.and(typAssignment, startTimePredicate, cb.not(cb.exists(notSub)), studentOnAssingment, taskDate, subjectPred));
        } else if (taskDate != null) {
            query.where(cb.and(typAssignment, startTimePredicate, cb.not(cb.exists(notSub)), studentOnAssingment, taskDate));
        } else if (subject != null) {
            subjectPred = cb.equal(assignments.get("subject"), subject);
            query.where(cb.and(typAssignment, startTimePredicate, cb.not(cb.exists(notSub)), studentOnAssingment, subjectPred));
        } else {
            query.where(cb.and(typAssignment, startTimePredicate, cb.not(cb.exists(notSub)), studentOnAssingment));
        }
        TypedQuery typedQuery = em.createQuery(query);
        List<Assignment> resultAssignments = typedQuery.getResultList();
        return resultAssignments;
    }

    @Override
    public List<Assignment> getAssignmentSubByTeam(Student studentInTeam, List<AssignmentSubmit.State> states, boolean areBeforeDeadline) {
        List<Team> studentsTeam = studentInTeam.getTeam();
        if (studentsTeam.isEmpty()) {
            return new ArrayList<Assignment>();
        }
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Assignment> query = cb.createQuery(Assignment.class);
        Root<Assignment> assignmentRoot = query.from(Assignment.class);
        Subquery submitSubquery = query.subquery(AssignmentSubmit.class);
        Root subRoot = submitSubquery.from(AssignmentSubmit.class);
        Join<Assignment, Team> teamAs = assignmentRoot.join("mTeams");
        Join<Team, Student> studnTeamAs = teamAs.join("mStudents");
        query.select(assignmentRoot);
        Predicate studentsOnAs = cb.equal(studnTeamAs, studentInTeam);
        Join<Team, AssignmentSubmit> assignmetSubmitTeam = subRoot.join("team");
        List<Predicate> predicates = new ArrayList<Predicate>();
        Iterator teamIt = studentsTeam.iterator();
        while (teamIt.hasNext()) {
            Team currTeam = (Team) teamIt.next();
            predicates.add(cb.equal(assignmetSubmitTeam, currTeam));
        }
        Predicate teamPredicate = cb.or(predicates.toArray(new Predicate[0]));
        Predicate assin = cb.equal(subRoot.get("assignment"), assignmentRoot);
        Predicate typeAssignment = cb.equal(assignmentRoot.get("type"), Assignment.Type.TEAM);
        predicates = new ArrayList<Predicate>();
        Iterator itState = states.iterator();
        while (itState.hasNext()) {
            predicates.add(cb.equal(subRoot.get("stateOfTask"), itState.next()));
        }
        if (!predicates.isEmpty()) {
            Predicate stateOfTask = cb.or(predicates.toArray(new Predicate[0]));

            submitSubquery.where(cb.and(teamPredicate, assin, stateOfTask));
        } else {

            submitSubquery.where(cb.and(teamPredicate, assin));
        }
        if (areBeforeDeadline) {
            Date currDate = new Maintance().getTodayDate();
            Path<Date> date = assignmentRoot.get("deadline");
            Predicate taskDate = cb.greaterThan(date, currDate);
            query.where(cb.and(cb.exists(submitSubquery), studentsOnAs, taskDate, typeAssignment));
        } else {
            query.where(cb.and(cb.exists(submitSubquery), studentsOnAs, typeAssignment));
        }
        query.orderBy(cb.desc(assignmentRoot.get("deadline")));
        TypedQuery typedQuery = em.createQuery(query);
        List<Assignment> resultAssignments = typedQuery.getResultList();
        return resultAssignments;
    }

    @Override
    public List<Assignment> getUnSubmitAssignment(List<GroupTaskPro> groups, Student student, boolean areBeforeDeadline,
            Subject subject) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Assignment> query = cb.createQuery(Assignment.class);
        Root<Assignment> queryRoot = query.from(Assignment.class);
        Subquery notSub = query.subquery(AssignmentSubmit.class);
        Root notSubRoot = notSub.from(AssignmentSubmit.class);
        Predicate studentOnNotSubmit = cb.equal(notSubRoot.get("student"), student);
        Predicate assignmentOnNotSubmit = cb.equal(notSubRoot.get("assignment"), queryRoot);
        Predicate groupOnAssignment = null;
        Iterator groupIt = groups.iterator();
        List<Predicate> predicates = new ArrayList<Predicate>();
        while (groupIt.hasNext()) {
            GroupTaskPro currGroup = (GroupTaskPro) groupIt.next();
            predicates.add(cb.equal(queryRoot.get("mGroups"), currGroup));
        }
        groupOnAssignment = cb.or(predicates.toArray(new Predicate[0]));
        Predicate subjectPred = null;
        if (subject != null) {
            subjectPred = cb.equal(queryRoot.get("subject"), subject);
        }
        notSub.where(cb.and(studentOnNotSubmit, assignmentOnNotSubmit));
        notSub.distinct(true);
        Predicate typAssignment = cb.equal(queryRoot.get("type"), Assignment.Type.GROUP);
        Predicate taskDate = null;
        Date currDate = Maintance.getTodayDate();
        if (areBeforeDeadline) {
            Path<Date> date = queryRoot.get("deadline");
            taskDate = cb.greaterThan(date, currDate);
        }
        Path<Date> dateOfStart = queryRoot.get("dateOfStart");
        Predicate startTimePredicate = cb.lessThan(dateOfStart, currDate);
        if ((taskDate != null) && (subjectPred != null)) {
            query.where(cb.and(cb.not(cb.exists(notSub)), startTimePredicate, typAssignment, groupOnAssignment, taskDate, subjectPred));
        } else if (taskDate != null) {
            query.where(cb.and(cb.not(cb.exists(notSub)), startTimePredicate, typAssignment, groupOnAssignment, taskDate));
        } else if (subjectPred != null) {
            query.where(cb.and(cb.not(cb.exists(notSub)), startTimePredicate, typAssignment, groupOnAssignment, subjectPred));
        } else {
            query.where(cb.and(cb.not(cb.exists(notSub)), startTimePredicate, typAssignment, groupOnAssignment));
        }
        query.distinct(true);
        TypedQuery typedQuery = em.createQuery(query);
        List<Assignment> resultAssignments = typedQuery.getResultList();
        return resultAssignments;
    }

    @Override
    public List<Assignment> getUnSubmitPersonalAssignments(Student student, Subject subject) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Assignment> query = cb.createQuery(Assignment.class);
        Root<Assignment> queryRoot = query.from(Assignment.class);
        Subquery notSub = query.subquery(AssignmentSubmit.class);
        Root notSubRoot = notSub.from(AssignmentSubmit.class);
        Predicate studentOnNotSubmit = cb.equal(notSubRoot.get("student"), student);
        Predicate assignmentOnNotSubmit = cb.equal(notSubRoot.get("assignment"), queryRoot);
        Predicate studentOnAssignment = cb.equal(queryRoot.get("mStudents"), student);
        notSub.where(cb.and(studentOnNotSubmit, assignmentOnNotSubmit));
        notSub.distinct(true);
        Predicate typAssignment = cb.equal(queryRoot.get("type"), Assignment.Type.STUDENT);
        query.distinct(true);
        query.where(cb.and(cb.not(cb.exists(notSub)), typAssignment, studentOnAssignment)).orderBy(cb.asc(queryRoot.get("dateOfAssignment")));
        TypedQuery typedQuery = em.createQuery(query);
        List<Assignment> resultAssignments = typedQuery.getResultList();
        return resultAssignments;
    }

    @Override
 public List<AssignmentSubmit> getAssignmentSubmitByContext(List<Student> mStudents,
            List<AssignmentSubmit.State> states,
            List<Subject> subject){
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<AssignmentSubmit> query = cb.createQuery(AssignmentSubmit.class);
        Root<AssignmentSubmit> queryRoot = query.from(AssignmentSubmit.class);
        Join<AssignmentSubmit, Assignment> asS = queryRoot.join("assignment");
        List<Predicate> predicates = new ArrayList<Predicate>();
        Predicate statePred = null;
        Predicate subjectPred = null;
        Predicate studentOnSubmit = null;
        Iterator stIterator = null;
        if (mStudents != null) {
            stIterator = mStudents.iterator();
            while (stIterator.hasNext()) {
                predicates.add(cb.equal(queryRoot.get("student"), stIterator.next()));
            }
            if (!predicates.isEmpty()) {
                studentOnSubmit = cb.or(predicates.toArray(new Predicate[0]));
                predicates = new ArrayList<Predicate>();
            }
        }
        if (subject != null) {
            stIterator = subject.iterator();
            while (stIterator.hasNext()) {
                predicates.add(cb.equal(asS.get("subject"), stIterator.next()));
            }
            if (!predicates.isEmpty()) {
                subjectPred = cb.or(predicates.toArray(new Predicate[0]));
                predicates = new ArrayList<Predicate>();
            }
        }
        if (states != null) {
            stIterator = states.iterator();
            while (stIterator.hasNext()) {
                predicates.add(cb.equal(queryRoot.get("stateOfTask"), stIterator.next()));
            }
            if (!predicates.isEmpty()) {
                statePred = cb.or(predicates.toArray(new Predicate[0]));
            }
        }
        Predicate typStudent = cb.equal(asS.get("type"), Assignment.Type.STUDENT);
        Predicate typGroup = cb.equal(asS.get("type"), Assignment.Type.GROUP);
        Predicate typeOfAs = cb.or(typGroup, typStudent);
        query.select(queryRoot);
        if ((subjectPred != null) && (statePred != null)) {
            query.where(cb.and(studentOnSubmit, subjectPred, typeOfAs, statePred));
        } else if (subjectPred != null) {
            query.where(cb.and(studentOnSubmit, subjectPred, typeOfAs));
        } else if (statePred != null) {
            query.where(cb.and(studentOnSubmit, statePred, typeOfAs));
        } else {
            query.where(cb.and(studentOnSubmit, typeOfAs));
        }
        query.distinct(true);
        //TODO query.orderBy(cb.asc(queryRoot.get("dateOfSubmit")));
        TypedQuery typedQuery = em.createQuery(query);
        List<AssignmentSubmit> resultAssignments = typedQuery.getResultList();
        return resultAssignments;
    }

    @Override
    public List<Assignment> getUnSubmitTeamAssignmet(Student student, boolean areBeforeDeadline, Subject subject) {
        List<Team> studentsTeam = student.getTeam();
        if (studentsTeam.isEmpty()) {
            return new ArrayList<Assignment>();
        }
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Assignment> aQuery = cb.createQuery(Assignment.class);
        Root<Assignment> rootQuery = aQuery.from(Assignment.class);
        Subquery notAs = aQuery.subquery(Assignment.class);
        Root<AssignmentSubmit> rootNotAs = aQuery.from(AssignmentSubmit.class);
        Join<Assignment, Team> teamAs = rootQuery.join("mTeams");
        Join<Team, Student> studnTeamAs = teamAs.join("mStudents");
        aQuery.select(rootQuery);
        Predicate studentOnAs = cb.equal(studnTeamAs, student);
        Join<Team, AssignmentSubmit> teamOnSubmit = rootNotAs.join("team");
        List<Predicate> predicates = new ArrayList<Predicate>();
        Iterator teamIt = studentsTeam.iterator();
        while (teamIt.hasNext()) {
            Team currTeam = (Team) teamIt.next();
            predicates.add(cb.equal(teamOnSubmit, currTeam));
        }
        Predicate teamPredicate = cb.or(predicates.toArray(new Predicate[0]));
        Predicate aPredicate = cb.equal(rootNotAs.get("assignment"), rootQuery);
        notAs.where(cb.and(aPredicate, teamPredicate));
        Predicate subjectPredicate = null;
        Predicate taskDate = null;
        Date currDate = Maintance.getTodayDate();
        if (areBeforeDeadline) {
            Path<Date> date = rootQuery.get("deadline");
            taskDate = cb.greaterThan(date, currDate);
        }
        Path<Date> dateOfStart = rootQuery.get("dateOfStart");
        Predicate startTimePredicate = cb.lessThan(dateOfStart, currDate);
        aQuery.distinct(true);
        if ((taskDate != null) && (subject != null)) {
            subjectPredicate = cb.equal(rootQuery.get("subject"), subject);
            aQuery.where(cb.and(studentOnAs, startTimePredicate, subjectPredicate, taskDate, cb.not(cb.exists(notAs))));
        } else if (taskDate != null) {
            aQuery.where(cb.and(studentOnAs, startTimePredicate, taskDate, cb.not(cb.exists(notAs))));
        } else if (subject != null) {
            subjectPredicate = cb.equal(rootQuery.get("subject"), subject);
            aQuery.where(cb.and(studentOnAs, startTimePredicate, subjectPredicate, cb.not(cb.exists(notAs))));
        } else {
            aQuery.where(cb.and(studentOnAs, startTimePredicate, cb.not(cb.exists(notAs))));
        }
        TypedQuery query = em.createQuery(aQuery);
        List<Assignment> resultList = query.getResultList();
        return resultList;
    }

    @Override
    public List<AssignmentSubmit> getTeamAssignmentSubmitByContext(Assignment assignment, Team team, List<State> state) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<AssignmentSubmit> query = cb.createQuery(AssignmentSubmit.class);
        Root<AssignmentSubmit> assignmentRoot = query.from(AssignmentSubmit.class);
        Predicate teamPredicate = cb.equal(assignmentRoot.get("team"), team);
        Predicate assing = cb.equal(assignmentRoot.get("assignment"), assignment);
        query.select(assignmentRoot);
        List<Predicate> predicates = new ArrayList<Predicate>();
        query.distinct(true);
        if (state != null) {
            Iterator itState = state.iterator();
            while (itState.hasNext()) {
                predicates.add(cb.equal(assignmentRoot.get("stateOfTask"), itState.next()));
            }
        }
        if (!predicates.isEmpty()) {
            Predicate stateOfTask = cb.or(predicates.toArray(new Predicate[0]));
            query.where(cb.and(stateOfTask, teamPredicate, assing));
        } else {
            query.where(cb.and(teamPredicate, assing));
        }
        List<AssignmentSubmit> result = em.createQuery(query).getResultList();
        return result;
    }

    @Override
    public List<Assignment> getAssignmentSubByContext(Student student, List<State> states, boolean areBeforeDeadline) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Assignment> query = cb.createQuery(Assignment.class);
        Root<Assignment> assignmentRoot = query.from(Assignment.class);
        Subquery<AssignmentSubmit> subQuery = query.subquery(AssignmentSubmit.class);
        Root<AssignmentSubmit> subQueryRoot = subQuery.from(AssignmentSubmit.class);
        Predicate studentOnSubmit = cb.equal(subQueryRoot.get("student"), student);
        Predicate assignmentOnSubmit = cb.equal(subQueryRoot.get("assignment"), assignmentRoot);
        Predicate typeStud = cb.equal(assignmentRoot.get("type"), Assignment.Type.STUDENT);
        Predicate typeGroup = cb.equal(assignmentRoot.get("type"), Assignment.Type.GROUP);
        Predicate type = cb.or(typeGroup, typeStud);
        List<Predicate> predicates = new ArrayList<Predicate>();
        if (states != null) {
            Iterator itState = states.iterator();
            while (itState.hasNext()) {
                predicates.add(cb.equal(subQueryRoot.get("stateOfTask"), itState.next()));
            }
        }
        if (!predicates.isEmpty()) {
            Predicate stateOfTask = cb.or(predicates.toArray(new Predicate[0]));
            subQuery.where(cb.and(studentOnSubmit, assignmentOnSubmit, stateOfTask));
        } else {
            subQuery.where(cb.and(studentOnSubmit, assignmentOnSubmit));
        }
        subQuery.distinct(true);
        if (areBeforeDeadline) {
            Date currDate = Maintance.getTodayDate();
            Path<Date> date = assignmentRoot.get("deadline");
            Predicate taskDate = cb.greaterThan(date, currDate);
            query.where(cb.and(cb.exists(subQuery), taskDate, type));
        } else {
            query.where(cb.and(cb.exists(subQuery), type));
        }
        query.orderBy(cb.desc(assignmentRoot.get("deadline")));
        TypedQuery typedQuery = em.createQuery(query);
        List<Assignment> resultAssignments = typedQuery.getResultList();
        return resultAssignments;

    }

    @Override
    public List<AssignmentSubmit> getAssignmentTeamToRateByContext(List<Student> mStudents, List<Subject> subject, List<AssignmentSubmit.State> states) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<AssignmentSubmit> query = cb.createQuery(AssignmentSubmit.class);
        Root<AssignmentSubmit> queryRoot = query.from(AssignmentSubmit.class);
        Join<Assignment, AssignmentSubmit> as = queryRoot.join("assignment");
        List<Predicate> predicates = new ArrayList<Predicate>();
        Predicate subjectToTeam = null;
        Predicate studentsInTeams = null;
        Predicate statePredicate = null;
        Iterator stI;
        if (subject != null) {
            stI = subject.iterator();
            while (stI.hasNext()) {
                predicates.add(cb.equal(as.get("subject"), stI.next()));
            }
            if (!predicates.isEmpty()) {
                subjectToTeam = cb.or(predicates.toArray(new Predicate[0]));
                predicates = new ArrayList<Predicate>();
            }
        }
        Predicate type = cb.equal(as.get("type"), Assignment.Type.TEAM);
        Join<Version, AssignmentSubmit> assignmentSubmitVersion = queryRoot.join("mVersions");
        Join<Student, Version> studentVersion = assignmentSubmitVersion.join("submittedStudent");
        Join<Team, Student> teamStudent = studentVersion.join("team");
        Join<Student, Team> studentTeam = teamStudent.join("mStudents");
        Join<GroupTaskPro, Team> group = teamStudent.join("group");
        Predicate subjectPr = cb.equal(group.get("subject"), as.get("subject"));
        if (mStudents != null) {
            stI = mStudents.iterator();
            while (stI.hasNext()) {
                predicates.add(cb.equal(studentTeam, stI.next()));
            }
            if (!predicates.isEmpty()) {
                studentsInTeams = cb.or(predicates.toArray(new Predicate[0]));
                predicates = new ArrayList<Predicate>();
            }
        }
        if (states != null) {
            stI = states.iterator();
            while (stI.hasNext()) {
                predicates.add(cb.equal(queryRoot.get("stateOfTask"), stI.next()));
            }
            if (!predicates.isEmpty()) {
                statePredicate = cb.or(predicates.toArray(new Predicate[0]));
            }
        }
        query.select(queryRoot);
        query.distinct(true);
        if ((subjectToTeam != null) && (statePredicate != null)) {
            query.where(cb.and(studentsInTeams, type, subjectPr, subjectToTeam, statePredicate));
        } else if (subjectToTeam != null) {
            query.where(cb.and(studentsInTeams, type, subjectPr, subjectToTeam));
        } else if (statePredicate != null) {
            query.where(cb.and(studentsInTeams, type, subjectPr, statePredicate));
        } else {
            query.where(cb.and(studentsInTeams, type, subjectPr));
        }
        TypedQuery typedQuery = em.createQuery(query);
        List<AssignmentSubmit> resultAssignments = typedQuery.getResultList();
        return resultAssignments;
    }

    @Override
    public List<Assignment> getAssignmentsByKantorAndSubject(Kantor kantor, List<Subject> subjects) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Assignment> queryAss = cb.createQuery(Assignment.class);
        Root<Assignment> queryAssRoot = queryAss.from(Assignment.class);
        Predicate subjectPredicate = null;
        if (subjects != null) {
            Iterator subIt = subjects.iterator();
            List<Predicate> predicates = new ArrayList<Predicate>();
            while (subIt.hasNext()) {
                Subject currSubject = (Subject) subIt.next();
                predicates.add(cb.equal(queryAssRoot.get("subject"), currSubject));
            }
            if (!predicates.isEmpty()) {
                subjectPredicate = cb.or(predicates.toArray(new Predicate[0]));
            }
        }
        Join<Assignment, Template> assTemplate = queryAssRoot.join("template");
        Predicate kantorPredicate = cb.equal(assTemplate.get("kantor"), kantor);
        if (subjectPredicate != null) {
            queryAss.where(cb.and(kantorPredicate, subjectPredicate));
        } else {
            queryAss.where(kantorPredicate);
        }
        queryAss.distinct(true).orderBy(cb.asc(queryAssRoot.get("dateOfAssignment")));
        TypedQuery typedQuery = em.createQuery(queryAss);
        List<Assignment> resultList = typedQuery.getResultList();
        return resultList;
    }

    @Override
    public void updateAssignment(Assignment assignmentToUpdate) {
        super.update(assignmentToUpdate);
    }

    @Override
    public List<AssignmentSubmit> getAssignmentSubmitsByAssignment(Assignment assignment) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<AssignmentSubmit> query = cb.createQuery(AssignmentSubmit.class);
        Root<AssignmentSubmit> assignments = query.from(AssignmentSubmit.class);
        Predicate assginmetPred = cb.equal(assignments.get("assignment"), assignment);
        query.where(assginmetPred).distinct(true);
        // TODO query.orderBy(cb.asc(assignments.get("dateOfSubmit")));
        List<AssignmentSubmit> result = em.createQuery(query).getResultList();
        return result;
    }

    @Override
    public List<AssignmentSubmit> getAssignmentSubmitByTeam(Team teamToAssignmentSubmit) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<AssignmentSubmit> query = cb.createQuery(AssignmentSubmit.class);
        Root<AssignmentSubmit> queryRoot = query.from(AssignmentSubmit.class);
        Predicate teamPredicate = cb.equal(queryRoot.get("team"), teamToAssignmentSubmit);
        query.select(queryRoot);
        query.distinct(true);
        query.where(teamPredicate);
        TypedQuery typedQuery = em.createQuery(query);
        List<AssignmentSubmit> resultAssignments = typedQuery.getResultList();
        return resultAssignments;
    }

    @Override
    public void removeAssignment(Assignment assignmentToRemove) {
        super.remove(assignmentToRemove);
    }


    @Override
    public List<AssignmentSubmit> getAssignmentSubmitsByKontext(Kantor kantor, List<Subject> subjects, List<AssignmentSubmit.State> states) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<AssignmentSubmit> query = cb.createQuery(AssignmentSubmit.class);
        Root<AssignmentSubmit> assignmentRoot = query.from(AssignmentSubmit.class);
        Join<Assignment, AssignmentSubmit> assingmentOnSubmit = assignmentRoot.join("assignment");
        Join<Template, Assignment> templateAssingmet = assingmentOnSubmit.join("template");
        Predicate kantorPredicate = cb.equal(templateAssingmet.get("kantor"), kantor);
        Predicate subjectPredicate = null;
        Predicate stateOfAssignmentPredicate = null;
        List<Predicate> predicates = new ArrayList<Predicate>();
        Iterator iterator = subjects.iterator();
        while (iterator.hasNext()) {
            predicates.add(cb.equal(assingmentOnSubmit.get("subject"), (Subject) iterator.next()));
        }
        subjectPredicate = cb.or(predicates.toArray(new Predicate[0]));
        predicates = new ArrayList<Predicate>();
        iterator = states.iterator();
        while (iterator.hasNext()) {
            predicates.add(cb.equal(assignmentRoot.get("stateOfTask"), iterator.next()));
        }
        stateOfAssignmentPredicate = cb.or(predicates.toArray(new Predicate[0]));
        query.where(cb.and(kantorPredicate, subjectPredicate, stateOfAssignmentPredicate)).distinct(true);
        //TODO query.orderBy(cb.desc(assignmentRoot.get("dateOfSubmit")));
        TypedQuery typedQuery = em.createQuery(query);
        List<AssignmentSubmit> resultList = typedQuery.getResultList();
        return resultList;
    }

    @Override
    public Template getTemplateById(long id) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Template> query = cb.createQuery(Template.class);
        Root<Template> templateRoot = query.from(Template.class);
        Predicate idPredicate = cb.equal(templateRoot.get("id"), id);
        query.where(idPredicate);
        TypedQuery typedQuery = em.createQuery(query);
        List<Template> resultList = typedQuery.getResultList();
        if (resultList.isEmpty()) {
            return null;
        }
        return resultList.get(0);
    }

    @Override
    public void removeTemplate(Template templateToRemove) {
        super.remove(templateToRemove);
    }

    @Override
    public List<Assignment> getAssignmentByTemplate(Template template) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Assignment> aQuery = cb.createQuery(Assignment.class);
        Root<Assignment> rootAQuery = aQuery.from(Assignment.class);
        Join<Template, Assignment> templateAssigment = rootAQuery.join("template");
        Predicate templatePredicate = cb.equal(templateAssigment, template);
        aQuery.where(templatePredicate).orderBy(cb.asc(rootAQuery.get("dateOfAssignment")));
        TypedQuery typedQuery = em.createQuery(aQuery);
        List<Assignment> resultList = typedQuery.getResultList();
        return resultList;
    }

    @Override
    public void addAssignmentSubmit(AssignmentSubmit assignmentSubmitToAdd) {
        super.add(assignmentSubmitToAdd);
    }

    @Override
    public void addVersion(Version version) {
        super.add(version);
    }

}